using System;
using System.Collections.Generic;
using System.Text;
using Mono.Cecil;
using Ninject.Core;
using TaoSharp.Core.Domain;

namespace TaoSharp.Core
{
    public class CecilAnalysisEngine : IAnalysisEngine
    {
        [Inject]
        public DataRepository Repository { get; set; }

        public void Process(string assemblyName)
        {
            AssemblyDefinition assemblyDefinition = AssemblyFactory.GetAssembly(assemblyName);
            int assemblyId = AddAssemblyItem(assemblyDefinition);

            BuildAssemblyReferences(assemblyDefinition, assemblyId);
            BuildNamespaceData(assemblyDefinition, assemblyId);
            BuildAssemblyGraphData(assemblyId);
        }

        private void BuildAssemblyGraphData(int id)
        {
            foreach (NamespaceItem item in Repository.Find<NamespaceItem>(x => x.AssemblyItemId == id))
            {
                NamespaceItem namespaceItem = item;
                
                int typeCount = Repository.Count<TypeItem>(x => x.NamespaceItemId == namespaceItem.Id);

                AssemblyGraphDataItem assemblyGraphDataItem = new AssemblyGraphDataItem
                                                          {
                                                              GraphType = "NAMESPACE",
                                                              Key = item.Name,
                                                              Value = typeCount.ToString()
                                                          };

                Repository.Add<AssemblyGraphDataItem>(assemblyGraphDataItem);
                Console.WriteLine("Added assembly graph data item:" + assemblyGraphDataItem);
            }
        }

        private void BuildAssemblyReferences(AssemblyDefinition assemblyDefinition, int assemblyId)
        {
            foreach (AssemblyNameReference assemblyReference in assemblyDefinition.MainModule.AssemblyReferences)
            {
                AssemblyReferenceItem assemblyReferenceItem = new AssemblyReferenceItem
                                                                  {
                                                                      AssemblyItemId = assemblyId,
                                                                      FullName = assemblyReference.FullName
                                                                  };
                Repository.Add<AssemblyReferenceItem>(assemblyReferenceItem);
            }
        }

        #region Private Methods

        private bool CompilerGenerated(CustomAttributeCollection attributes)
        {
            // TODO: Make an Exists extension method on Collection and then use it here
            foreach (CustomAttribute attribute in attributes)
            {
                if ("System.Runtime.CompilerServices.CompilerGeneratedAttribute".Equals(attribute.Constructor.DeclaringType.ToString()))
                {
                    return true;
                }
            }

            return false;
        }

        private void BuildNamespaceData(AssemblyDefinition assemblyDefinition, int assemblyId)
        {
            var namespaces = new Dictionary<string, NamespaceItem>();

            foreach (TypeDefinition typeDef in GetAssemblyTypes(assemblyDefinition))
            {
                NamespaceItem n;

                if (CompilerGenerated(typeDef.CustomAttributes))
                    continue;

                // if tDef.Namespace is not in Dictionary then add the namespace 
                if (!namespaces.ContainsKey(typeDef.Namespace))
                {
                    n = AddNamespaceItem(typeDef.Namespace, assemblyId);
                    namespaces.Add(typeDef.Namespace, n);
                }
                else
                {
                    n = namespaces[typeDef.Namespace];
                }

                AddTypeItem(n.Id, typeDef);
            }

            Logger.Current.Debug(string.Format("Total namespaces: {0}", namespaces.Count));
        }

        private void AddTypeItem(int namespaceItemId, TypeDefinition typeDef)
        {
            var isGeneric = typeDef.GenericParameters.Count > 0 ? true : false;
            var genericParameterString = string.Empty;
            if (isGeneric)
                genericParameterString = string.Format("<{0}>", BuildGenericString(typeDef.GenericParameters));

            var typeData = new TypeItem
                               {
                                   FullName =
                                       string.Format("{0}{1}", typeDef.FullName.Split('`')[0], genericParameterString),
                                   Name = string.Format("{0}{1}", typeDef.Name.Split('`')[0], genericParameterString),
                                   NamespaceItemId = namespaceItemId,
                                   IsGeneric = isGeneric
                               };

            int newTypeId = Repository.Add<TypeItem>(typeData);
            BuildMethodData(typeDef, newTypeId);
        }

        private void BuildMethodData(TypeDefinition typeDef, int typeId)
        {
            foreach (MethodDefinition methodDefinition in typeDef.Methods)
            {
                AddMethodItem(methodDefinition, typeId);
            }
        }

        private int AddMethodItem(IMemberReference methodDefinition, int typeId)
        {
            var methodItem = new MethodItem
                                 {
                                     Name = methodDefinition.Name,
                                     DeclaringType = methodDefinition.DeclaringType.Name,
                                     TypeItemId = typeId
                                 };

            return Repository.Add<MethodItem>(methodItem);
        }

        private static string BuildGenericString(GenericParameterCollection parameters)
        {
            var tempString = new StringBuilder();
            foreach (GenericParameter parameter in parameters)
            {
                tempString.Append(parameter.Name);
            }

            return tempString.Join(',');
        }

        private NamespaceItem AddNamespaceItem(string aNamespaceItem, int assemblyId)
        {
            var namespaceItem = new NamespaceItem {Name = aNamespaceItem, AssemblyItemId = assemblyId};
            Repository.Add<NamespaceItem>(namespaceItem);

            return namespaceItem;
        }

        private int AddAssemblyItem(AssemblyDefinition definition)
        {
            var assemblyItem = new AssemblyItem
                                   {
                                       EntryPoint = (definition.EntryPoint != null)
                                                        ? definition.EntryPoint.Name
                                                        : "",
                                       FullName = definition.Name.FullName,
                                       Name = definition.Name.Name,
                                       Version = definition.Name.Version.ToString()
                                   };

            return Repository.Add<AssemblyItem>(assemblyItem);
        }

        private static IList<TypeDefinition> GetAssemblyTypes(AssemblyDefinition asmDef)
        {
            var types = new List<TypeDefinition>();
            foreach (TypeDefinition t in asmDef.MainModule.Types)
            {
                types.Add(t);
            }

            return types;
        }

        #endregion

    }
}